import { defineComponent, onMounted, onBeforeMount, ref, watch } from 'vue'
import Virtual from './virtual'
import Item from '@/components/Chat/VirtualList/item.tsx'

// 范围区间
export interface Range {
  start: number
  end: number
  //实现虚拟列表的关键，获取范围，获取范围后的数据项个数，
  // 获取范围后的数据项的偏移量，获取范围后的数据项的索引偏移量，获取范围后的数据项的索引范围，
  // 获取范围后的数据项的索引范围中的第一个索引，获取范围后的数据项的索引范围中的最后一个索引
  padFront: number
  padBehind: number
}

//获取数据源
interface DataSource {
  [key: string]: any
}

//使用tsx语法
export default defineComponent({
  name: 'VirtualList',
  //组件需要传递进来的属性
  props: {
    // 数据
    data: {
      type: Array,
      required: true,
      default: () => [],
    },
    // 唯一标识键值
    dataKey: {
      type: [String, Function],
      required: true,
    },
    // 数据项组件，传入遍历的组件
    item: {
      type: [Object, Function],
      required: true,
    },
    // Item项的Props
    itemProps: {
      type: Object,
    },
    // 指定用什么名字传递数据
    dataPropName: {
      type: String,
      default: 'source',
    },
    // 可视区域内保留的数据项个数
    keeps: {
      type: Number,
      default: 30,
    },
    // 元素预估高度 元素的预估高度（进行预加载）
    // 用于在未测量实际高度前的计算
    size: {
      type: Number,
      default: 50,
    },
    // 起始索引-用来指定默认从哪里开始渲染
    start: {
      type: Number,
      default: 0,
    },
    // 偏移量
    offset: {
      type: Number,
      default: 0,
    },
    // 顶部触发阈值
    topThreshold: {
      type: Number,
      default: 0,
    },
    // 底部触发阈值
    bottomThreshold: {
      type: Number,
      default: 0,
    },
  },
  emits: ['scroll', 'totop', 'tobottom', 'resized', 'ok'],
  setup(props, { emit, expose }) {
    //范围区
    const range = ref<Range | null>(null)
    //虚拟组件的引用
    const rootRef = ref<HTMLElement | null>()
    //锚点元素的引用
    const shepherd = ref<HTMLDivElement | null>(null)
    //虚拟列表的渲染计算对象
    let virtual: Virtual

    onBeforeMount(() => {
      virtual = new Virtual(
        {
          // 头部插槽的高度，用于计算滚动偏移量时的修正值
          slotHeaderSize: 0,
          // 底部插槽的高度，同样用于滚动计算的修正
          slotFooterSize: 0,
          // 可视区域内保持渲染的元素数量，决定了同时渲染在DOM中的元素个数
          keeps: props.keeps,
          // 元素的预估高度，用于在未测量实际高度前的计算(用于计算前后padding需要填充的高度)
          estimateSize: 50,
          // 缓冲区大小，用于提前渲染临近区域的元素，避免快速滚动时出现白屏
          buffer: props.keeps / 3,
          // 数据源中每个元素的唯一标识数组，用于标识和追踪每个列表项
          uniqueIds: getSourceUniqueIds(),
        },
        callBackHandle,
      )

      range.value = virtual.getRange()
    })

    //监听数组长度变化
    watch(
      () => props.data.length,
      () => {
        //更新virtual的参数
        virtual.updateParam('uniqueIds', getSourceUniqueIds())

        //跟新结束位置
        virtual.handleDataChange()
      },
    )

    //监听可视区域个数
    watch(
      () => props.keeps,
      () => {
        //更新虚拟列表的参数
        virtual.updateParam('keeps', props.keeps)

        virtual.handleDataChange()
      },
    )

    //监听偏移量的变化（滚动之后，子组件更新这个数据）
    watch(
      () => props.offset,
      (newValue) => {
        //平滑滚动
        scrollToOffset(newValue)
      },
    )

    // 监听起始索引变化，开始索引（滚动之后，子组件更新这个数据）
    watch(
      () => props.start,
      (newValue) => {
        scrollToIndex(newValue)
      },
    )

    const scrollToIndex = (index: number, smooth?: boolean, topDistance = 0) => {
      if (index >= props.data.length - 1) {
        // 滚动到底部
        scrollToBottom(smooth)
      } else {
        //没有滑动到底部,滚动到指定位置
        scrollToLocation(index, smooth, topDistance)
      }
    }

    const scrollToLocation = (index: number, smooth?: boolean, topDistance = 0) => {
      const offset = virtual.getOffsetByIndex(index) - topDistance
      scrollToOffset(offset, smooth)
    }

    const scrollToBottom = (smooth?: boolean) => {
      if (shepherd.value) {
        const offset = shepherd.value.clientTop
        scrollToOffset(offset, smooth)

        setTimeout(() => {
          if (scrollTopOffset() + clientHeight() < scrollHeight()) {
            scrollToBottom(smooth)
          }
        }, 3)
      }
    }

    /**
     * 滚动到指定偏移量
     * @param offset 滚动条偏移量
     * @param smooth 是否平滑滚动
     */
    const scrollToOffset = (offset: number, smooth = false) => {
      if (rootRef.value) {
        rootRef.value.scrollTo({
          left: 0,
          top: offset,
          behavior: smooth ? 'smooth' : 'auto',
        })
      }
    }

    //获取数据源的唯一标识符
    const getSourceUniqueIds = () => {
      const { dataKey, data = [] } = props
      // const key = dataKey as DataSource
      return data.map((item: any) => {
        return typeof dataKey === 'function' ? dataKey(item) : item[dataKey]
      })
    }

    //回调用事件（通过virtual.ts计算完成，需要渲染的start，和end下标）
    const callBackHandle = (rangePara: Range) => {
      range.value = rangePara
    }

    //滚动条滚动的偏移量
    const scrollTopOffset = () => {
      return Math.ceil(rootRef.value?.scrollTop || 0)
    }

    //虚拟列表可视区
    const clientHeight = () => {
      return Math.ceil(rootRef.value?.clientHeight || 0)
    }

    //滚动条高度
    const scrollHeight = () => {
      return Math.ceil(rootRef.value?.scrollHeight || 0)
    }

    //监听虚拟列表滚动事件
    const virtualScrollHandler = () => {
      //滚动条滚动偏移量
      const top = scrollTopOffset()
      //可视区高度
      const clientH = clientHeight()
      //滚动条高度
      const height = scrollHeight()
      //滚动条滚动偏移量小于0或者滚动条滚动偏移量大于滚动条高度时，不处理
      if (top < 0 || top + clientH < height || height!) {
        return
      }
      //处理滚动事件
      virtual.handleScroll(top)
      //向父组件发送滚动事件
      // emit('scrolled', top, clientH, height)
      emitEvent(top, clientH, height)
    }

    // 统一处理向外暴露事件
    const emitEvent = (offset: number, clientSize: number, scrollSize: number) => {
      //emit('scroll', { offset, clientSize, scrollSize })

      //滚动条向前滚动，数据列表不为空，且滚动条偏移量小于等于顶部阈值时触发totop事件
      if (virtual.isFront() && !!props.data.length && offset - props.topThreshold <= 0) {
        emit('totop')
      }
      //滚动条向后滚动，数据列表不为空，且滚动条偏移量加上可视区域大小加上底部阈值大于等于总高度时触发tobottom事件
      else if (virtual.isBehind() && offset + clientSize + props.bottomThreshold >= scrollSize) {
        emit('tobottom')
      }
    }

    //暴露给父组件的方法，父类可以直接调用暴露的这些方法
    expose({
      scrollToBottom,
      // getSizes,
      // getSize,
      // getOffset,
      // getScrollSize,
      // getClientSize,
      scrollToOffset,
      scrollToIndex,
    })

    //子item的高度发生变化回调方法
    const itemResizeHandler = (key: string, height: number) => {
      //监听到子项的高度变化，重新计算虚拟列表范围
      virtual.saveSize(key, height)
      emit('resized', key, height)
    }

    /**
     * 渲染getRenderSlots函数
     */
    const getRenderSlots = () => {
      const slots = []
      const { start, end } = range.value!
      const { data, item, dataKey, itemProps, dataPropName } = props
      for (let index = start; index <= end; index++) {
        const dataSource: DataSource = data[index] as DataSource
        if (dataSource) {
          //获取key
          const key = typeof dataKey === 'function' ? dataKey(dataSource) : dataSource[dataKey]
          //渲染组件
          if (typeof key === 'string' || typeof key === 'number') {
            slots.push(
              <Item
                index={index}
                uniqueKey={key}
                source={dataSource}
                itemProps={itemProps}
                dataPropName={dataPropName}
                component={item}
                onResizeItem={itemResizeHandler}
              />,
            )
          }
        }
      }
      //console.log('slots', slots)
      return slots
    }

    // 组件模板
    return () => {
      const { padFront, padBehind } = range.value!
      return (
        // 通过上下撑起来，避免滚动条闪动，模拟出真实的滚动效果
        <div
          ref={rootRef}
          style={{ paddingTop: padFront, paddingBottom: padBehind }}
          onScroll={virtualScrollHandler}
        >
          {/*渲染虚拟列表的每个item*/}
          <div>{getRenderSlots()}</div>
          {/*底部*/}
          <div style={{ width: '100%', height: '0px' }} ref={shepherd}></div>
        </div>
      )
    }
  },
})
